package rpg.writer.scripts.impl;

import java.awt.Color;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

import rpg.client.model.Actor;
import rpg.client.model.Actor.Gender;
import rpg.client.model.ActorInfo;
import rpg.client.model.Dialog;
import rpg.client.model.DialogResponse;
import rpg.client.model.Position;
import rpg.engine.client.GameObjectInfo;
import rpg.engine.model.DialogInfo;
import rpg.engine.model.GameEffect;
import rpg.engine.model.GameEffect.Type;
import rpg.engine.model.GameMap;
import rpg.engine.model.MagicArea;
import rpg.engine.model.Mover;
import rpg.engine.model.When;
import rpg.engine.model.World;
import rpg.util.Strings;
import rpg.writer.scripts.ScriptWarning;
import rpg.writer.scripts.impl.ParsedScript.Builder;

public class ScriptAnalyzer implements AnalyzerContext {
	public static final String DEFAULT_ACT_NAME = "DEFAULT";

	private static final Color BROWN = new Color(139, 69, 19);

	public static final String PREFIX_SCENE = "SCENE: ";

	public static final String PREFIX_ACT = "ACT: ";

	public static final String PREFIX_ACTOR = "[Actor";

	public static final String PREFIX_EFFECT = "[GameEffect";
	public static final String PREFIX_EVENT = "[GameEvent";
	public static final String PREFIX_MAGIC_AREA = "[MagicArea";
	public static final String PREFIX_MOVER = "[Mover";
	public static final String PREFIX_OBJECT = "[Object";
	public static final String PREFIX_USE = "[Use";
	public static final String PREFIX_WEAR = "[Wear";
	public static final String PREFIX_WHEN = "[When";
	public static final String PREFIX_RESPONSE = "->";
	public static final String PREFIX_ANSWER = "@";

	static String findFirstNotWhenLine(
			final SortedMap<Integer, String> linesInAGroup) {
		for (final String s : linesInAGroup.values()) {
			if (!s.startsWith(PREFIX_WHEN)) {
				return s;
			}
		}
		return null;
	}

	static void xyz(final Position.Builder b, final String input) {
		final int comma = input.indexOf(",");
		final String x = Strings.trim(input.substring(0, comma));
		final String y = Strings
				.trim(input.substring(comma + 1, input.length()));
		b.x(Integer.valueOf(x));
		b.y(Integer.valueOf(y));
	}

	private final Collection<ScriptWarning> warnings = new ArrayList<ScriptWarning>();

	private final String storyID;
	private final List<String> lines;
	private final ScriptBuffer buffer;
	private int sceneCount = 0;

	private String currentMapID = "";

	private String currentActID = ScriptAnalyzer.DEFAULT_ACT_NAME;

	public ScriptAnalyzer(final String storyID, final List<String> lines) {
		super();
		this.storyID = storyID;
		this.lines = lines;
		this.buffer = new ScriptBuffer(storyID, warnings);
	}

	private String addMap(final Integer lineNumber, final String line) {
		sceneCount++;
		currentMapID = buffer.addMap(line.substring(PREFIX_SCENE.length()),
				(sceneCount == 1));
		return currentMapID;
	}

	void addMapMover(final Integer lineNumber, final String line) {
		final Mover.Builder mb = new Mover.Builder();

		final int space0 = line.indexOf(" ");
		final int b0Open = line.indexOf("(");

		final String optionalID = Strings.trim(line.substring(space0, b0Open));
		if (optionalID.length() > 0) {
			mb.id(storyID + ":" + optionalID);
		} else {
			mb.id(storyID + ":MV" + lineNumber);
		}

		// From Position
		final int b0Close = line.indexOf(")");
		final Position.Builder fpb = new Position.Builder();
		fpb.map(currentMapID);
		xyz(fpb, line.substring(b0Open + 1, b0Close));
		mb.from(fpb.build());

		// Description
		final int qOpen = line.indexOf("'");
		final int qClose = line.lastIndexOf("'");
		mb.description(line.substring(qOpen + 1, qClose));

		// Destination
		final int comma = line.indexOf(",", qClose);
		final int b1Open = line.lastIndexOf("(");
		final int b1Close = line.lastIndexOf(")");
		final Position.Builder tpb = new Position.Builder();
		tpb.map(Strings.trim(line.substring(1 + comma, b1Open)));
		xyz(tpb, line.substring(b1Open + 1, b1Close));
		mb.to(tpb.build());

		// Inactive
		mb.active(!line.contains("INACTIVE"));

		final Mover m = mb.build();

		buffer.add(currentMapID, m);
	}

	private void addObject(final Integer lineNumber, final String line) {
		final String WEARABLE = "WEARABLE";

		// [Object Console (2,5) 'Console' INACTIVE]
		GameObjectInfo.Builder gob = new GameObjectInfo.Builder();

		// set ID
		final int firstSpace = line.indexOf(" ");
		final int secondSpace = line.indexOf(" ", firstSpace + 1);
		final String gameObjectID = storyID + ":"
				+ line.substring(1 + firstSpace, secondSpace);
		gob = gob.id(gameObjectID);

		// set Location
		final Position.Builder pb = new Position.Builder();
		pb.map(currentMapID);
		final int b1 = line.indexOf("(");
		final int b2 = line.indexOf(")");
		if (b2 > b1) {
			xyz(pb, line.substring(b1 + 1, b2));
			gob = gob.position(pb.build());
		}

		// set name
		final int q0 = line.indexOf("'");
		final int q1 = line.indexOf("'", 1 + q0);
		gob = gob.name(line.substring(q0 + 1, q1));

		gob = gob.active(!line.contains("INACTIVE"));

		if (line.contains(WEARABLE)) {
			gob.wearable(true);
		}

		buffer.add(gameObjectID, gob);
	}

	@Override
	public void addWarning(final ScriptWarning w) {
		if (w != null) {
			warnings.add(w);
		}
	}

	void analyze(final SortedMap<Integer, String> linesInAGroup) {
		if (linesInAGroup.isEmpty()) {
			return;
		}

		final int size = linesInAGroup.size();

		for (final Map.Entry<Integer, String> entry : linesInAGroup.entrySet()) {
			final Integer lineNumber = entry.getKey();
			final String line = entry.getValue();

			if (size == 1) {
				processSingleLine(lineNumber, line, true);
				break;
			}

			final String firstNotWhenLine = findFirstNotWhenLine(linesInAGroup);

			if (line.startsWith(PREFIX_MAGIC_AREA)) {
				groupMagicArea(linesInAGroup);
				break;
			} else if (line.startsWith(PREFIX_ACTOR)) {
				warnings.add(ScriptWarning.notImplemented(lineNumber,
						firstNotWhenLine, "single line of actors only"));
			} else if (line.startsWith(PREFIX_WEAR)) {
				groupWearObject(linesInAGroup);
				break;
			} else if (line.startsWith(PREFIX_USE)) {
				groupUse(linesInAGroup);
				break;
			} else if (line.startsWith(PREFIX_SCENE)) {
				groupGameMap(linesInAGroup);
				break;
			} else if (line.startsWith(PREFIX_ANSWER)) {
				groupDialog(linesInAGroup);
				break;
			} else if (firstNotWhenLine.contains(":")) {
				groupDialog(linesInAGroup);
				break;
			} else if (line.startsWith(PREFIX_EFFECT)) {
				if (sceneCount == 1) {
					groupStartEffects(linesInAGroup);
					break;
				} else {
					warnings.add(ScriptWarning.notRecognized(lineNumber, line,
							"in second scene, no start effects here."));
				}
			} else {
				warnings.add(ScriptWarning.notRecognized(lineNumber, line,
						"prefix not matched"));
			}
		}
	}

	public World create() {
		final List<SortedMap<Integer, String>> groups = findGroups();

		for (final SortedMap<Integer, String> group : groups) {
			debug(group);
			analyze(group);
		}

		final ParsedScript script = finishScript();

		return WorldTool.build(script);
	}

	private void debug(final Object o) {
		System.out.println("\nScriptAnalyzer:\n" + o);
	}

	private void debug(final SortedMap<Integer, String> map) {
		System.out.println("");
		for (final Map.Entry<Integer, String> entry : map.entrySet()) {
			System.out.println(entry.getKey() + ") " + entry.getValue());
		}
	}

	private List<SortedMap<Integer, String>> findGroups() {
		final List<SortedMap<Integer, String>> groups = new ArrayList<SortedMap<Integer, String>>();

		SortedMap<Integer, String> currentMap = new TreeMap<Integer, String>();
		int lineNumber = 0;
		for (final String rawLine : lines) {
			lineNumber++;

			final String line = Strings.trim(rawLine);
			if (line.startsWith("#")) {
				continue;
			}

			if (line.length() == 0) {
				if (!currentMap.isEmpty()) {
					currentMap = new TreeMap<Integer, String>();
				}
			} else {
				if (currentMap.isEmpty()) {
					groups.add(currentMap);
				}
				currentMap.put(lineNumber, line);
			}
		}

		return groups;
	}

	private Position findPlace(final GameMap map, final List<Position> used) {
		System.out.println(map + ") " + used);
		if (map == null) {
			return Position.VOID;
		}

		int x = 2;
		int y = 0;
		while (y < map.getRows().size()) {
			final char[] chars = map.getRows().get(y).toCharArray();
			if (chars.length > x) {
				if (isPlaceFree(x, y, chars[x], used)) {
					final Position.Builder pb = new Position.Builder();
					pb.map(map.getId());
					pb.x(x);
					pb.y(y);
					final Position p = pb.build();
					used.add(p);
					return p;
				}
			} else {
				x = -2;
				y += 2;
			}

			x += 2;
		}

		return Position.VOID;
	}

	private Map<String, GameMap> finishGameMaps(Builder b,
			final Map<String, GameMap.Builder> mapOfMaps,
			final Map<String, LinkedList<String>> mapsRows) {
		final Map<String, GameMap> maps = new LinkedHashMap<String, GameMap>();

		for (final Map.Entry<String, GameMap.Builder> entry : mapOfMaps
				.entrySet()) {
			final String mapID = entry.getKey();
			final GameMap.Builder gmb = entry.getValue();

			final LinkedList<String> lines = mapsRows.get(mapID);
			if (lines.isEmpty()) {
				gmb.row(".....");
				gmb.row(".....");
				gmb.row(".....");
				gmb.row(".....");
				gmb.row(".....");
			} else {
				for (final String line : lines) {
					gmb.row(line);
				}
			}

			final GameMap gm = gmb.build();
			maps.put(gm.getId(), gm);

			b = b.gameMap(gmb.build());
		}

		return maps;
	}

	ParsedScript finishScript() {
		ParsedScript.Builder b = new ParsedScript.Builder(storyID);

		final Map<String, GameMap> maps = finishGameMaps(b, buffer.mapOfMaps,
				buffer.mapsRows);

		final Collection<DialogInfo> dis = buffer.makeDialogInfo();
		for (final DialogInfo di : dis) {
			b.dialog(di);
		}

		for (final MagicArea ma : buffer.getMagicAreas()) {
			b = b.magicArea(ma);
		}

		for (final GameEffect e : buffer.getStartEffects()) {
			b = b.effect(e);
		}

		final Map<String, List<Position>> placesUsed = new HashMap<String, List<Position>>();

		// Add Actors

		for (final Actor.Builder ab : buffer.getActorBuilders()) {
			final Actor a = ab.build();
			debug(a);
			final ActorInfo.Builder aib = new ActorInfo.Builder();
			final Position.Builder pb = buffer.mapActorPositions.get(a.getId());
			final String startingMapID = buffer.actorPlaces.get(a.getId());

			List<Position> used = placesUsed.get(startingMapID);
			if (used == null) {
				used = new ArrayList<Position>();
				placesUsed.put(startingMapID, used);
			}

			final Position place;

			if (pb != null) {
				pb.map(startingMapID);
				place = pb.build();
				used.add(place);
			} else if (startingMapID != null) {
				final GameMap map = maps.get(startingMapID);
				place = findPlace(map, used);
			} else {
				place = Position.VOID;
			}

			aib.position(place);
			aib.actor(a);

			b = b.actor(aib.build());
		}

		// Add Objects

		for (final GameObjectInfo go : buffer.getObjects()) {
			b = b.object(go);
		}

		final ParsedScript script = b.build();

		return script;
	}

	@Override
	public final String getStoryID() {
		return storyID;
	}

	public Collection<ScriptWarning> getWarnings() {
		return warnings;
	}

	private void groupDialog(final Map<Integer, String> linesInAGroup) {
		// 14) [When INVENTORY_HAS $20]
		// 15) Wizard: Do you want to buy a magic ring for $20?
		// 16) -> Yes. [1]
		// 17) -> No thanks. [2]

		boolean firstLine = true;
		String chatID = null;

		final List<When> whens = new ArrayList<When>();

		for (final Map.Entry<Integer, String> entry : linesInAGroup.entrySet()) {
			final Integer lineNumber = entry.getKey();
			final String line = entry.getValue();

			// Assume the first is a line of dialog and the rest our effects
			if (line.startsWith(PREFIX_WHEN)) {
				final When when = WhenTool.parse(lineNumber, line, this);
				if (when != null) {
					whens.add(when);
				}
			} else if (firstLine) {
				final boolean startEffect = (sceneCount == 1);
				chatID = parseSingleDialog(lineNumber, line, (sceneCount > 1));
				firstLine = false;
				buffer.addWhensToLastDialog(whens, chatID);

				if (startEffect) {
					final GameEffect.Builder geb = new GameEffect.Builder();
					geb.fromID(chatID);
					geb.storyID(storyID);
					geb.type(Type.START_CHAT);
					final GameEffect ge = geb.build();
					buffer.addStartEffect(ge);
				}
			} else if (line.startsWith(PREFIX_EFFECT)) {
				final GameEffect ge = GameEffectTool.parse(lineNumber, line,
						this);
				buffer.addEffectToLastDialog(ge, chatID);
			} else if (line.startsWith(PREFIX_RESPONSE)) {
				final DialogResponse dr = parseDialogResponse(lineNumber, line);
				if (dr != null) {
					buffer.addResponseToLastDialog(dr, chatID);
				}
			} else {
				warnings.add(ScriptWarning.notValidThere(lineNumber, line,
						"TBD"));
			}
		}
	}

	private void groupGameMap(final Map<Integer, String> linesInAGroup) {
		boolean first = true;

		for (final Map.Entry<Integer, String> entry : linesInAGroup.entrySet()) {
			final Integer lineNumber = entry.getKey();
			final String line = entry.getValue();

			if (first) {
				addMap(lineNumber, line);
				first = false;
			} else if (line.startsWith(PREFIX_MOVER)) {
				addMapMover(lineNumber, line);
			} else if (line.startsWith(PREFIX_OBJECT)) {
				addObject(lineNumber, line);
			} else if (line.startsWith(PREFIX_ACTOR)) {
				parsePlaceActor(line);
			} else if (line.startsWith("[")) {
				warnings.add(ScriptWarning.notValidThere(lineNumber, line,
						"TBD"));
			} else {
				buffer.addMapRow(currentMapID, line);
			}
		}
	}

	void groupMagicArea(final Map<Integer, String> linesInAGroup) {
		boolean first = true;
		MagicArea.Builder mab = null;

		for (final Map.Entry<Integer, String> entry : linesInAGroup.entrySet()) {
			final Integer lineNumber = entry.getKey();
			final String line = entry.getValue();

			if (first) {
				mab = magicAreaBuilder(lineNumber, line);
				first = false;
			} else {
				final String id = processSingleLine(lineNumber, line, false);
				if (id.length() > 0) {
					final GameEffect.Builder geb = new GameEffect.Builder();

					geb.type(GameEffect.Type.START_CHAT);
					geb.storyID(storyID);
					geb.fromID(id);

					mab.effect(geb.build());
				}
			}
		}

		final MagicArea ma = mab.build();
		buffer.add(ma);
	}

	private void groupStartEffects(
			final SortedMap<Integer, String> linesInAGroup) {
		for (final Map.Entry<Integer, String> entry : linesInAGroup.entrySet()) {
			final Integer lineNumber = entry.getKey();
			final String line = entry.getValue();

			if (line.startsWith(PREFIX_EFFECT)) {
				final GameEffect ge = GameEffectTool.parse(lineNumber, line,
						this);
				buffer.addStartEffect(ge);
			} else {
				warnings.add(ScriptWarning.notRecognized(lineNumber, line,
						"TBD"));
			}
		}
	}

	private void groupUse(final SortedMap<Integer, String> linesInAGroup) {
		boolean first = true;

		String gameObjectID = storyID + ":";

		String firstChatID = "";

		for (final Map.Entry<Integer, String> entry : linesInAGroup.entrySet()) {
			final Integer lineNumber = entry.getKey();
			final String line = entry.getValue();

			if (first) {
				// [Use Console]
				final int firstSpace = line.indexOf(" ");
				gameObjectID += line.substring(1 + firstSpace,
						line.length() - 1);
				first = false;
			} else if (line.startsWith(PREFIX_EFFECT)) {
				final GameEffect ge = GameEffectTool.parse(lineNumber, line,
						this);
				buffer.addEffectToObject(gameObjectID, ge);
			} else if (line.startsWith(PREFIX_EVENT)) {
				warnings.add(ScriptWarning.notImplemented(lineNumber, line,
						"TBD"));
			} else if (line.contains(":")) {
				if (firstChatID.length() > 0) {
					warnings.add(ScriptWarning.notImplemented(lineNumber, line,
							"Only one chat can be started in a useGroup"));
					continue;
				}
				firstChatID = parseSingleDialog(lineNumber, line, false);
				final GameEffect.Builder geb = new GameEffect.Builder();
				geb.type(GameEffect.Type.START_CHAT);
				geb.storyID(storyID);
				geb.fromID(firstChatID);
				buffer.addEffectToObject(gameObjectID, geb.build());
			} else if (line.startsWith(PREFIX_RESPONSE)) {
				if (firstChatID.length() == 0) {
					warnings.add(ScriptWarning.notImplemented(lineNumber, line,
							"TBD"));
				} else {
					final DialogResponse dr = parseDialogResponse(lineNumber,
							line);
					buffer.addResponseToLastDialog(dr, firstChatID);
				}
			} else {
				warnings.add(ScriptWarning.notImplemented(lineNumber, line,
						"TBD"));
			}
		}
	}

	private void groupWearObject(final SortedMap<Integer, String> linesInAGroup) {
		// [Wear Ring]
		// [When ACT_IS Intro]
		// [GameEffect GENDER_CHANGE Player FEMALE]
		// [GameEffect CHANGE_ACT Your A Girl]
		boolean firstLine = true;
		String gameObjectID = "";

		for (final Map.Entry<Integer, String> entry : linesInAGroup.entrySet()) {
			final Integer lineNumber = entry.getKey();
			final String line = entry.getValue();

			if (firstLine) {
				final int space0 = line.indexOf(" ");
				gameObjectID = storyID + ":"
						+ line.substring(1 + space0, line.length() - 1);
				firstLine = false;

				final When.Builder wb = new When.Builder();
				wb.storyID(storyID);
				wb.type(When.Type.IS_WEARING);
				buffer.addWhenToObject(gameObjectID, wb.build());
			} else if (line.startsWith(PREFIX_WHEN)) {
				final When when = WhenTool.parse(lineNumber, line, this);
				buffer.addWhenToObject(gameObjectID, when);
			} else if (line.startsWith(PREFIX_EFFECT)) {
				final GameEffect ge = GameEffectTool.parse(lineNumber, line,
						this);
				buffer.addEffectToObject(gameObjectID, ge);
			} else {
				warnings.add(ScriptWarning.notRecognized(lineNumber, line,
						"TBD"));
			}
		}
	}

	private boolean isPlaceFree(final int x, final int y, final char c,
			final List<Position> used) {
		if (c != '.') {
			return false;
		}

		for (final Position p : used) {
			if (p.getX() != x) {
				continue;
			}
			if (p.getY() == y) {
				return false;
			}
		}
		return true;
	}

	MagicArea.Builder magicAreaBuilder(final Integer lineNumber,
			final String line) {
		final Position.Builder fp = new Position.Builder();
		fp.map(currentMapID);

		final Position.Builder tp = new Position.Builder();
		tp.map(currentMapID);

		final int bracketOpen1 = line.indexOf("(");
		final int bracketClose1 = line.indexOf(")");
		xyz(fp, line.substring(bracketOpen1 + 1, bracketClose1));

		final int bracketOpen2 = line.lastIndexOf("(");
		final int bracketClose2 = line.lastIndexOf(")");
		xyz(tp, line.substring(bracketOpen2 + 1, bracketClose2));

		final MagicArea.Builder mab = new MagicArea.Builder();
		mab.id("MA" + lineNumber);
		mab.from(fp.build());
		mab.to(tp.build());
		return mab;
	}

	private DialogResponse parseDialogResponse(final Integer lineNumber,
			final String line) {
		// -> Yes. [1]
		int b0 = line.lastIndexOf("[");
		final int b1 = line.lastIndexOf("]");

		String toChatID = "";
		if (b1 > b0) {
			toChatID = "@" + Strings.trim(line.substring(1 + b0, b1));
		}

		if (b0 < 0) {
			b0 = line.length();
		}
		return new DialogResponse(Strings.trim(line.substring(
				PREFIX_RESPONSE.length(), b0)), storyID, toChatID);
	}

	private void parsePlaceActor(final String line) {
		// [Actor Chief (1,5)]
		final int space1 = line.indexOf(" ");
		final int space2 = line.indexOf(" ", space1 + 1);
		final String actorID = Strings.trim(line.substring(space1, space2));

		final Position.Builder pb = new Position.Builder();
		pb.map(currentMapID);
		final int b1 = line.indexOf("(");
		final int b2 = line.indexOf(")");
		xyz(pb, line.substring(b1 + 1, b2));

		buffer.setActorPosition(actorID, pb);
	}

	String parseSingleDialog(final Integer lineNumber, String _line,
			final boolean _intro) {
		final String RETAIN_FLAG = "[RETAIN]";

		// @1 Wizard: I'm sure you will be happy with it.
		final String chatID;
		final String line;
		final boolean intro;
		final boolean retain = _line.endsWith(RETAIN_FLAG);
		if (retain) {
			_line = _line.substring(0, _line.length() - RETAIN_FLAG.length());
		}

		if (_line.startsWith("@")) {
			intro = false;
			final int space0 = _line.indexOf(" ");
			if (space0 > 0) {
				chatID = _line.substring(0, space0);
				line = _line.substring(1 + space0);
			} else {
				chatID = _line.substring(0, _line.length());
				line = "";
			}
		} else {
			intro = _intro;
			String tempChatID = "" + lineNumber;
			while (tempChatID.length() < 8) {
				tempChatID = "0" + tempChatID;
			}
			chatID = "C" + tempChatID;
			line = _line;
		}

		final boolean firstScene = (sceneCount == 1);

		Dialog.Mode mode = Dialog.Mode.TALKING;

		final int colon = line.indexOf(":");
		if (colon < 0) {
			final Dialog.Builder db = new Dialog.Builder();
			db.id(chatID);
			db.makeNull();
			final DialogInfo.Builder dib = new DialogInfo.Builder();
			dib.intro(false);
			dib.once(false);
			buffer.addDialogInfo(chatID, db, dib);
			return chatID;
		}

		final String prefix = Strings.trim(line.substring(0, colon));
		String name = prefix;

		if (prefix.startsWith("(")) {
			final int comma = prefix.indexOf(",");
			if (comma >= 0) {
				final String newMode = Strings.trim(prefix.substring(comma + 1,
						prefix.length() - 1));
				mode = Dialog.Mode.valueOf(newMode);
				name = Strings.trim(prefix.substring(1, comma));
			}
		}

		if (Dialog.Mode.NARRATION.name().equals(name)) {
			mode = Dialog.Mode.NARRATION;
		}

		final String dialogText = Strings.trim(line.substring(colon + 1));

		final Actor.Builder ab = new Actor.Builder();
		final String ACTOR_ID = name;
		ab.id(ACTOR_ID);
		ab.name(name);

		// Try and store the first place an actor appears.
		if (mode == Dialog.Mode.TALKING) {
			buffer.firstPlace(name, currentMapID);
		}
		buffer.add(ACTOR_ID, ab);

		final Dialog.Builder db = new Dialog.Builder();
		db.id(chatID);
		db.actorId(ACTOR_ID);
		db.text(dialogText);
		db.mode(mode);

		// final Dialog dialog = db.build();
		final DialogInfo.Builder dib = new DialogInfo.Builder();
		dib.once(!retain);
		if (!DEFAULT_ACT_NAME.equals(currentActID)) {
			final When.Builder wb = new When.Builder();
			wb.type(When.Type.ACT_IS);
			wb.storyID(storyID);
			wb.targetID(currentActID);
			final When when = wb.build();
			dib.when(when);
		}

		dib.intro(intro);

		buffer.addDialogInfo(chatID, db, dib);

		if ((firstScene) && (intro)) {
			final GameEffect.Builder geb = new GameEffect.Builder();
			geb.type(Type.START_CHAT);
			geb.storyID(storyID);
			geb.fromID(chatID);
			final GameEffect ge = geb.build();
			buffer.addStartEffect(ge);
		}

		return chatID;
	}

	private String processSingleLine(final Integer lineNumber,
			final String line, final boolean intro) {
		if (line.startsWith(PREFIX_SCENE)) {
			return addMap(lineNumber, line);
		} else if (line.startsWith(PREFIX_ACTOR)) {
			// [Actor Rachel FEMALE {Hair: RED}]

			final int space0 = line.indexOf(" ");
			final int space1 = line.indexOf(" ", space0 + 1);

			final String actorID = line.substring(1 + space0, space1);
			// Check for Female
			if (line.contains("FEMALE")) {
				buffer.setGender(actorID, Gender.FEMALE);
			}

			// Properties
			final int s0 = line.indexOf("{");
			final int s1 = line.indexOf("}", s0 + 1);
			if (s1 > s0) {
				final String props = line.substring(s0 + 1, s1);
				final String[] items = props.split(",");
				for (final String item : items) {
					final int colon = item.indexOf(":");
					if (colon < 1) {
						warnings.add(ScriptWarning.notRecognized(lineNumber,
								line, "TBD"));
						continue;
					}
					final String key = item.substring(0, colon);
					if ("Hair".equals(key)) {
						final String value = item.substring(1 + colon);
						final Color c = toColour(value, lineNumber, line);
						buffer.setHairColour(actorID, c);
					}
				}
			}

		} else if (line.startsWith(PREFIX_ACT)) {
			currentActID = line.substring(PREFIX_ACT.length());
		} else if (line.startsWith(PREFIX_EFFECT)) {
			final boolean firstScene = (sceneCount == 1);
			if (firstScene) {
				final GameEffect ge = GameEffectTool.parse(lineNumber, line,
						this);
				buffer.addStartEffect(ge);
			} else {
				warnings.add(ScriptWarning.notValidThere(lineNumber, line,
						"TBD"));
			}
		} else if (line.startsWith("#")) {
		} else if (line.contains(":")) {
			final String chatID = parseSingleDialog(lineNumber, line, intro);
			// final boolean startEffect = (sceneCount == 1);
			// if (startEffect) {
			// GameEffect.Builder geb = new GameEffect.Builder();
			// geb.fromID(chatID);
			// geb.storyID(storyID);
			// geb.type(Type.START_CHAT);
			// GameEffect ge = geb.build();
			// buffer.addStartEffect(ge);
			// }
			return chatID;
		}

		else {
			warnings.add(ScriptWarning.notRecognized(lineNumber, line, "TBD"));
		}
		return "";
	}

	Color toColour(final String value, final Integer lineNumber,
			final String line) {
		final String v = Strings.trim(value);
		if ("RED".equals(v)) {
			return Color.RED;
		} else if ("WHITE".equals(v)) {
			return Color.WHITE;
		} else if ("BLONDE".equals(v)) {
			return Color.YELLOW;
		} else if ("BROWN".equals(v)) {
			return ScriptAnalyzer.BROWN;
		} else if ("BLACK".equals(v)) {
			return Color.BLACK;
		} else {
			warnings.add(ScriptWarning.notRecognized(lineNumber, line, "TBD"));
		}
		return null;
	}
}
